package com.kgc.gateway.config;

import com.alibaba.fastjson.JSON;
import com.kgc.gateway.filter.CustomAccessDeniedHandler;
import com.kgc.gateway.filter.CustomAuthenticationEntryPoint;
import com.kgc.personcommon.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private CustomAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    /**
     * 1. 定义所有匿名可访问的路径
     *    - 登录、验证码、刷新、登出、预检(OPTIONS) 都无需 token
     */
    private static final String[] AUTH_WHITELIST = {
            "/per/user/login",
            "/per/user/getCaptcha",
            "/per/user/refresh",
            "/per/user/logout",
            "/public/**"
    };

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
                .csrf().disable()
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .authorizeExchange(exchanges -> exchanges
                        // 放行所有 OPTIONS
                        .pathMatchers(HttpMethod.OPTIONS).permitAll()
                        // 放行白名单中的路径
                        .pathMatchers(AUTH_WHITELIST).permitAll()
                        // 其余接口交给自定义的动态鉴权
                        .anyExchange().access(dynamicAuthorizationManager())
                )
                // 在 Spring Security 的 AUTHENTICATION 阶段插入我们的 JWT 认证过滤器
                .addFilterAt(jwtAuthFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                .exceptionHandling(e -> e
                        .authenticationEntryPoint(authenticationEntryPoint)
                        .accessDeniedHandler(accessDeniedHandler)
                )
                .build();
    }

    /**
     * 2. JWT 认证管理器：从 token 中解析用户名 & 权限列表，构造 Authentication
     */
    @Bean
    public ReactiveAuthenticationManager jwtAuthenticationManager() {
        return authentication -> {
            String token = (String) authentication.getCredentials();
            if (!jwtUtil.validateToken(token)) {
                return Mono.error(new BadCredentialsException("Invalid or expired token"));
            }

            String username = jwtUtil.getUsernameFromToken(token);
            // ✅ 从 Redis 中获取权限列表（接口路径）
            String redisKey = "user:" + username + ":permissions";
            String permsJson = redisTemplate.opsForValue().get(redisKey);

            if (permsJson == null) {
                return Mono.error(new BadCredentialsException("No permissions found in Redis"));
            }

            List<String> apis = JSON.parseArray(permsJson, String.class);
            List<SimpleGrantedAuthority> auths = apis.stream()
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());

            return Mono.just(
                    new UsernamePasswordAuthenticationToken(username, null, auths)
            );
        };
    }

//    @Bean
//    public ReactiveAuthenticationManager jwtAuthenticationManager() {
//        return authentication -> {
//            String token = (String) authentication.getCredentials();
//            if (!jwtUtil.validateToken(token)) {
//                return Mono.error(new BadCredentialsException("Invalid or expired token"));
//            }
//            String username = jwtUtil.getUsernameFromToken(token);
//            List<String> apis = jwtUtil.getAuthFromToken(token);
//            List<SimpleGrantedAuthority> auths = apis.stream()
//                    .map(SimpleGrantedAuthority::new)
//                    .collect(Collectors.toList());
//            return Mono.just(
//                    new UsernamePasswordAuthenticationToken(username, null, auths)
//            );
//        };
//    }

    /**
     * 3. JWT 认证过滤器：拦截除白名单外的所有请求，提取并校验 Authorization 头
     */
    private AuthenticationWebFilter jwtAuthFilter() {
        AuthenticationWebFilter filter = new AuthenticationWebFilter(jwtAuthenticationManager());

        // 只在不在白名单时才进行 token 校验
        filter.setRequiresAuthenticationMatcher(
                new NegatedServerWebExchangeMatcher(
                        ServerWebExchangeMatchers.pathMatchers(AUTH_WHITELIST)
                )
        );

        // 从 Authorization: Bearer <token> 中提取 token
        filter.setServerAuthenticationConverter(exchange -> {
            String header = exchange.getRequest()
                    .getHeaders()
                    .getFirst(HttpHeaders.AUTHORIZATION);
            if (header != null && header.startsWith("Bearer ")) {
                String token = header.substring(7);
                return Mono.just(
                        new UsernamePasswordAuthenticationToken(token, token)
                );
            }
            return Mono.empty();
        });

        // 无状态，不存 session
        filter.setSecurityContextRepository(NoOpServerSecurityContextRepository.getInstance());

        // 认证成功后继续执行下一过滤器
        filter.setAuthenticationSuccessHandler((webFilterExchange, authentication) ->
                webFilterExchange.getChain().filter(webFilterExchange.getExchange())
        );

        return filter;
    }

    /**
     * 4. 动态鉴权：用 Ant 风格匹配请求路径，未匹配到则拒绝
     */
    private ReactiveAuthorizationManager<AuthorizationContext> dynamicAuthorizationManager() {
        return (monoAuth, ctx) -> monoAuth
                .filter(Authentication::isAuthenticated)
                .flatMapIterable(auth -> auth.getAuthorities())
                .map(GrantedAuthority::getAuthority)
                .any(perm -> pathMatcher.match(perm, ctx.getExchange().getRequest().getPath().value()))
                .map(AuthorizationDecision::new)
                // 如果权限列表里根本没有任何匹配，就拒绝
                .defaultIfEmpty(new AuthorizationDecision(false));
    }

    /**
     * 5. CORS 配置：只允许前端 http://localhost:5173 发请求
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(Collections.singletonList("http://localhost:5173"));
        config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(Collections.singletonList("*"));
        config.setAllowCredentials(true);
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}
